iT邦幫忙

2022 iThome 鐵人賽

DAY 7
0
Modern Web

那些年我們一起 review 的 code系列 第 7

那些年我們一起 review 的 code:多國語系

  • 分享至 

  • xImage
  •  

遇到多國語系最麻煩的無非就是 key 該如何訂定?有些人覺得應該語意化,有些人覺得應該直接按照檔案路徑,在激烈的爭辯後,考量到未來的擴充性,我們最後決定採用檔案結構為依歸,並且預設語言一律為英文。我們是使用 react-intl 這個套件實作多國語系,當初在 i18n 跟 intl 上決定了許久,由於 i18n 只將重點擺在語言上,並不包含日期時間、幣值、數字表示法等不同國家文化的國際化,因此最後選擇了比較廣用的 react-intl

翻譯檔案位置

我們的資料夾結構如下:

src/
    components/
        common/
            component1.tsx
            component2.tsx
            translation.ts
        featureA/
            componentA1.tsx
            componentA2.tsx
            componentA3.tsx
            translation.ts
        featureB/
            componentB1.tsx
            componentB2.tsx
            translation.ts

每個 feature(domain) 資料夾都會有一個翻譯檔案,因為對於不同的 feature(domain) 來說,context 也會差異很大。如果是有些共用的元件,像是按鈕、Modal 等等,則是直接使用 common 這個共用的 feature(domain),因此 translation 檔案的管理也比較容易。

在 react 裡面習慣會將頁面切分出來管理,上面描述的是關於元件層級的翻譯管理,至於頁面層級的翻譯管理,我們使用以下結構:

src/
    pages/
        translation.ts
        PageA/
            index.ts
            PageA.tsx
            PageComponentA1.tsx
            PageComponentA2.tsx
        PageB/
            index.ts
            PageB.tsx
            PageComponentB1.tsx

由於 react 大部分都是元件化的關係,因此我們不需要每個 page 裡面都有 translation 檔案,這樣管理上複雜度會增加不少,因此我們統一使用一個 translation 檔案來管理頁面層級的翻譯。

翻譯的 key

由於我們是依照檔案位置來命名,加上我們為了確保一致性,因此統一採用三層命名:{domain}.{component}.{item},如下所示:

// src/components/activity/translation.ts
import { defineMessages } from "react-intl";

const activityMessages = {
  "*": defineMessages({
    add: { id: "activity.*.add", defaultMessage: "新增" },
  }),
  ActivitySessionItem: defineMessages({
    attended: {
      id: "activity.ActivitySessionItem.attended",
      defaultMessage: "已簽到",
    },
    attendNow: {
      id: "activity.ActivitySessionItem.attendNow",
      defaultMessage: "立即簽到",
    },
    enterLinkPage: {
      id: "activity.ActivitySessionItem.enterLinkPage",
      defaultMessage: "進入直播頁面",
    },
    add: { id: "activity.ActivitySessionItem.add", defaultMessage: "新增" },
  }),
};

export default activityMessages;

比較特別的是如果同一個 featur(domain) 裡面的元件有共用的部分,像是「儲存」、「取消」,這種共用部分我們會使用 * 的方式來命名,如此一來也可以避免一直在寫相同的 key。如果是頁面層級的翻譯的話則類似,只是 feature(domain) 的部分統一使用:page。

// src/pages/translation.ts
import { defineMessages } from "react-intl";

const pageMessages = {
  "*": defineMessages({
    cancel: { id: "page.*.cancel", defaultMessage: "取消" },
  }),
  // ProgramContentPage
  ProgramContentPage: defineMessages({
    foo: { id: "page.ProgramContentPage.foo", defaultMessage: "Foo Message" },
    bar: { id: "page.ProgramContentPage.bar", defaultMessage: "Bar Message" },
  }),
  ProgramContentTabs: defineMessages({
    foo: { id: "page.ProgramContentTabs.foo", defaultMessage: "Foo Message" },
    bar: { id: "page.ProgramContentTabs.bar", defaultMessage: "Bar Message" },
  }),
  // ProgramPage
  ProgramPage: defineMessages({
    foo: { id: "page.ProgramPage.foo", defaultMessage: "Foo Message" },
    bar: { id: "page.ProgramPage.bar", defaultMessage: "Bar Message" },
  }),
};

export default pageMessages;

上一篇
那些年我們一起 review 的 code:分頁處理
下一篇
那些年我們一起 review 的 code:錯誤處理
系列文
那些年我們一起 review 的 code9
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言